using Gobang;
using System;
using System.Collections.Generic;
using Tools;
using UnityEngine;

namespace MCTS
{
    public static class MCTSUtility
    {
        public class Comparer_Value : IComparer<Vector2Int>, IComparer<MonteCarloTreeNode>
        {
            public Dictionary<Vector2Int, float> valueTable;

            public Comparer_Value(Dictionary<Vector2Int, float> valueTable)
            {
                this.valueTable = valueTable;
            }

            public int Compare(Vector2Int x, Vector2Int y)
            {
                return valueTable[y].CompareTo(valueTable[x]);
            }

            public int Compare(MonteCarloTreeNode x, MonteCarloTreeNode y)
            {
                return y.valueSelf.CompareTo(x.valueSelf);
            }
        }

        public class Comparer_Random : IComparer<Vector2Int>
        {
            private int seed;

            public Comparer_Random()
            {
                Randomize();
            }

            public void Randomize()
            {
                seed = random.Next();
            }

            public int Compare(Vector2Int x, Vector2Int y)
            {
                float Value(Vector2Int v)
                {
                    return HashCode.Combine(v, seed);
                }

                return Value(x).CompareTo(Value(y));
            }
        }
        public class Comparer_MonteCarloTreeNode_UCB : IComparer<MonteCarloTreeNode>
        {
            public MonteCarloTreeNode parent;

            public Comparer_MonteCarloTreeNode_UCB(MonteCarloTreeNode parent)
            {
                this.parent = parent;
            }

            public int Compare(MonteCarloTreeNode x, MonteCarloTreeNode y)
            {
                if (parent == null)
                    return 0;
                return y.UCB.CompareTo(x.UCB);
            }
        }
        public class Comparer_MonteCarloTreeNode_LCB : IComparer<MonteCarloTreeNode>
        {
            public MonteCarloTreeNode parent;

            public Comparer_MonteCarloTreeNode_LCB(MonteCarloTreeNode parent)
            {
                this.parent = parent;
            }

            public int Compare(MonteCarloTreeNode x, MonteCarloTreeNode y)
            {
                if (parent == null)
                    return 0;
                return y.LCB.CompareTo(x.LCB);
            }
        }

        public static Vector2Int[] Directions =
        {
            Vector2Int.up,
            Vector2Int.up + Vector2Int.right,
            Vector2Int.right,
            Vector2Int.right + Vector2Int.down
        };


        public static Comparer_Random CR;
        public static MCTSSettings settings;
        public static System.Random random;

        static MCTSUtility()
        {
            random = new System.Random((int)DateTime.Now.Ticks);
            settings = Resources.Load<MCTSSettings>("MCTSSettings");
            CR = new Comparer_Random();
        }

        public static float UCB(float q, int n, int n_parent, float actionValue, bool match)
        {
            if (!match)
                q = n - q;
            int period = Mathf.Clamp(n_parent / settings.periodLength, 0, settings.peroidWeights.Length - 1);
            return q / n + settings.c_confidenceBound * Mathf.Sqrt(2 * Mathf.Log(n_parent) / n) + settings.k_UCB * settings.peroidWeights[period] * actionValue;
        }

        public static float LCB(float q, int n, int n_parent,float actionValue)
        {
            int period = Mathf.Clamp(n_parent / settings.periodLength, 0, settings.peroidWeights.Length - 1);
            return q / n - settings.c_confidenceBound * Mathf.Sqrt(2 * Mathf.Log(n_parent) / n) + settings.k_LCB * settings.peroidWeights[period] * actionValue;
        }

        public static float CalculateValue(ChessboardState state, Vector2Int action, bool raw = false)
            => CalculateValue(state.board, state.chesses.Count, action, state.nextChess, raw);
        public static float CalculateValue(int[,] board,int chessCount, Vector2Int action, int nextChess, bool raw = false)
        {
            float self = CalculateValue(board, chessCount, action, nextChess);
            float ret;
            if (self < 400)
            {
                float other = CalculateValue(board, chessCount, action, 3 - nextChess);
                float position = CalculateValueOfPosition(action, board.GetLength(0));
                ret = MathTool.CalculateNorm(settings.p_norm, self, settings.k_other * other) + position;
                if (!raw)
                    ret = Mathf.Clamp(ret / 400f, 0f, 1f);
            }
            else
            {
                ret = raw ? self : 100f;
            }
            return ret;
        }
        private static float CalculateValueOfPosition(Vector2Int action, int size)
        {
            int sqr = new Vector2Int(action.x - size / 2, action.y - size / 2).sqrMagnitude;
            return 1f / (1f + sqr);
        }
        private static float CalculateValue(int[,] board, int chessCount, Vector2Int action, int assumption)
        {
            int sum = 0;
            for (int i = 0; i < Directions.Length; i++)
            {
                sum += CalculateValueOnDirection(board, action, assumption, Directions[i]);
            }
            return sum * 10f / (1 + chessCount);
        }
        private static int CalculateValueOnDirection(int[,] board, Vector2Int action, int assumption, Vector2Int direction)
        {
            Vector2Int Offset(int n)
                => action + n * direction;
            int GetChess(Vector2Int v)
                => board[v.x, v.y];

            int size = board.GetLength(0);

            int right = 0;
            while (right <= 3)
            {
                Vector2Int v = Offset(right + 1);
                if (!ChessboardUtility.Contain(size, v) || GetChess(v) + assumption == 3)
                    break;
                right++;
            }

            int left = 0;
            while (left >= -3)
            {
                Vector2Int v = Offset(left - 1);
                if (!ChessboardUtility.Contain(size, v) || GetChess(v) + assumption == 3)
                    break;
                left--;
            }

            int max = 0;
            int original, modified;
            for (int i = left; i + 4 <= right; i++)
            {
                original = 0;
                for (int j = i; j < i + 5; j++)
                {
                    original <<= 1;
                    Vector2Int v = Offset(j);
                    if (GetChess(v) != 0)
                        original |= 1;
                }
                modified = original | (1 << (4 + i));
                bool leftBlocked = i == left && (!ChessboardUtility.Contain(size, Offset(i - 1)) || GetChess(Offset(i - 1)) + assumption == 3);
                bool rightBlocked = i + 4 == right && (!ChessboardUtility.Contain(size, Offset(i + 5)) || GetChess(Offset(i + 5)) + assumption == 3);
                int value = CalculateValueOfCode(modified, leftBlocked, rightBlocked) - CalculateValueOfCode(original, leftBlocked, rightBlocked);
                max = Mathf.Max(max, value);
            }
            return max;
        }

        private readonly static int[] LocalValues = new int[32] { 0, 1, 1, 4, 1, 2, 4, 16, 1, 1, 2, 12, 4, 12, 16, 64, 1, 1, 1, 6, 2, 4, 12, 48, 4, 6, 12, 16, 16, 48, 64, 512 };
        private static int CalculateValueOfCode(int localCode, bool leftBlocked, bool rightBlocked)
        {
            int value = LocalValues[localCode];
            if (leftBlocked && localCode != 31 && (localCode & 0b10000) != 0)
                value >>= 2;
            if (rightBlocked && localCode != 31 && (localCode & 1) != 0)
                value >>= 2;
            return value;
        }
    }
}